/**
 * 
 */
package it.unitn.testbed.server.config;

import it.unitn.testbed.client.common.MoteConfig;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.SchemaFactory;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Testbed configuration that parses a testbed XML configuration file.
 * 
 * The schema of the XML file is setup by {@link TestbedConfig#SCHEMA_LOCATION}.
 * The object is instantiated using an {@code InputStream} and, upon
 * instantiation, it automatically parses the XML.
 * 
 * @author Stefan Guna
 * 
 */
public class TestbedConfig extends DefaultHandler {
	/**
	 * A list of registered objects interested in receiving notification of
	 * deployment changes.
	 */
	private static ArrayList<ConfigUser> configUsers = new ArrayList<ConfigUser>();
	public static Logger log = Logger.getLogger(TestbedConfig.class.getName());
	/** The resource name denoting the schema of the configuration file */
	private static final String SCHEMA_LOCATION = "testbedConfigSchema.xsd";
	/**
	 * The XML namespace for the deployment configuration file saved when this
	 * servlet is destroyed.
	 */
	private static final String XML_NAMESPACE = "http://d3s.unitn.it/testbedConfigSchema";

	/**
	 * Returns the lsit of registered configuration users.
	 * 
	 * @return the configUsers
	 */
	protected static ArrayList<ConfigUser> getConfigurationUsers() {
		return configUsers;
	}

	/**
	 * Registers a new object to be notified whenever a change of configuration
	 * occurs.
	 * 
	 * @param configUser
	 *            The object to be registered.
	 * @see #unregisterService(ConfigUser)
	 */
	public static void registerService(ConfigUser configUser) {
		configUsers.add(configUser);
		log.trace("Testbed service register, count=" + configUsers.size());
	}

	/**
	 * Unregistered an object from the notification service.
	 * 
	 * @param configUser
	 *            The object to unregister.
	 *@see #registerService(ConfigUser)
	 */
	public static void unregisterService(ConfigUser configUser) {
		configUsers.remove(configUser);
		log.trace("Testbed service unregister, count=" + configUsers.size());
	}

	private ArrayList<String> consumers = new ArrayList<String>();
	private String emailFrom;
	private ArrayList<MoteConfig> motes = new ArrayList<MoteConfig>();
	private ArrayList<String> recipients = new ArrayList<String>();

	/**
	 * @return the recipients
	 */
	public ArrayList<String> getRecipients() {
		return recipients;
	}

	private String smtpServer;
	private String sshPassword;

	private String sshUser;

	/**
	 * Instantiates a new configuration parser object and runs the parser.
	 * 
	 * @param config
	 *            The input stream holding the XML to parse.
	 * @throws SAXException
	 * @throws ParserConfigurationException
	 * @throws IOException
	 */
	public TestbedConfig(InputStream config) throws SAXException,
			ParserConfigurationException, IOException {
		SAXParserFactory factory = SAXParserFactory.newInstance();
		factory.setNamespaceAware(true);
		factory.setValidating(false);

		SchemaFactory schemaFactory = SchemaFactory
				.newInstance("http://www.w3.org/2001/XMLSchema");

		InputStream schemaStream = TestbedConfig.class
				.getResourceAsStream(SCHEMA_LOCATION);

		factory.setSchema(schemaFactory
				.newSchema(new Source[] { new StreamSource(schemaStream) }));

		SAXParser parser = factory.newSAXParser();

		parser.parse(config, this);
	}

	/**
	 * Saves the deployment configuration file.
	 * 
	 * @param out
	 *            The outbut stream.
	 * @throws ParserConfigurationException
	 * @throws FileNotFoundException
	 * @throws TransformerException
	 */
	public void dumpConfigXml(OutputStream out)
			throws ParserConfigurationException, TransformerException {

		DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
				.newInstance();
		DocumentBuilder documentBuilder = documentBuilderFactory
				.newDocumentBuilder();
		Document document = documentBuilder.newDocument();

		Element deployment = document.createElementNS(XML_NAMESPACE,
				"deployment");
		document.appendChild(deployment);

		if (sshUser != null) {
			Element ssh = document.createElementNS(XML_NAMESPACE, "sshConfig");
			ssh.setAttribute("username", sshUser);
			ssh.setAttribute("password", sshPassword);
			deployment.appendChild(ssh);
		}

		Element mail = document.createElementNS(XML_NAMESPACE, "mailConfig");
		mail.setAttribute("smtp", smtpServer);
		mail.setAttribute("from", emailFrom);
		deployment.appendChild(mail);

		Element recipients = document.createElementNS(XML_NAMESPACE,
				"recipients");
		for (String recipient : this.recipients) {
			Element recipientEl = document.createElementNS(XML_NAMESPACE,
					"recipient");
			recipientEl.setAttribute("email", recipient);
			recipients.appendChild(recipientEl);
		}
		deployment.appendChild(recipients);

		Element consumersElement = document.createElementNS(XML_NAMESPACE,
				"consumers");

		for (String consumerClass : consumers) {
			Element consumer = document.createElementNS(XML_NAMESPACE,
					"consumer");
			consumer.setAttribute("class", consumerClass);
			consumersElement.appendChild(consumer);
		}

		if (consumersElement.hasChildNodes())
			deployment.appendChild(consumersElement);

		Element motesElement = document.createElementNS(XML_NAMESPACE, "motes");
		for (MoteConfig moteConfig : motes) {
			Element mote = document.createElementNS(XML_NAMESPACE, "mote");
			mote.setAttribute("id", moteConfig.getId().toString());
			mote.setAttribute("source", moteConfig.getSource());
			mote.setAttribute("sf", moteConfig.getSerialFw());
			if (moteConfig.getSshSFStart() != null)
				mote.setAttribute("sfStart", moteConfig.getSshSFStart());
			if (moteConfig.getSshSFStop() != null)
				mote.setAttribute("sfStop", moteConfig.getSshSFStop());
			motesElement.appendChild(mote);
		}
		deployment.appendChild(motesElement);

		TransformerFactory transformerFactory = TransformerFactory
				.newInstance();

		Transformer transformer = transformerFactory.newTransformer();
		transformer.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1");
		transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		StreamResult result = new StreamResult(out);
		DOMSource source = new DOMSource(document);
		transformer.transform(source, result);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.xml.sax.helpers.DefaultHandler#error(org.xml.sax.SAXParseException)
	 */
	@Override
	public void error(SAXParseException e) throws SAXException {
		log.error("Error parsing deployment file: " + e.getMessage());
		motes = null;
		throw e;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.xml.sax.helpers.DefaultHandler#fatalError(org.xml.sax.SAXParseException
	 * )
	 */
	@Override
	public void fatalError(SAXParseException e) throws SAXException {
		log.fatal("Fatal error parsing deployment file: " + e.getMessage());
		motes = null;
		throw e;
	}

	/**
	 * @return the consumers
	 */
	public ArrayList<String> getConsumers() {
		return consumers;
	}

	/**
	 * @return the emailFrom
	 */
	public String getEmailFrom() {
		return emailFrom;
	}

	/**
	 * Gets the mote configuration for a given ID.
	 * 
	 * @param id
	 *            The ID of the mote.
	 * @return the mote configuration of mote ID.
	 */
	public MoteConfig getMoteConfig(int id) {
		for (MoteConfig mote : motes) {
			if (mote.getId() == id)
				return mote;
		}
		return null;
	}

	/**
	 * Getter for the configuration.
	 * 
	 * @return The configuration obtained from parsing the XML.
	 */
	public ArrayList<MoteConfig> getMotes() {
		return motes;
	}

	/**
	 * @return the smtpServer
	 */
	public String getSmtpServer() {
		return smtpServer;
	}

	/**
	 * @return the sshPassword
	 */
	public String getSshPassword() {
		return sshPassword;
	}

	/**
	 * @return the sshUser
	 */
	public String getSshUser() {
		return sshUser;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String,
	 * java.lang.String, java.lang.String, org.xml.sax.Attributes)
	 */
	@Override
	public void startElement(String uri, String localName, String qName,
			Attributes attributes) throws SAXException {
		if (motes == null)
			return;
		if (localName.equals("mote")) {
			MoteConfig moteConfig = new MoteConfig(new Integer(attributes
					.getValue("id")), attributes.getValue("source"), attributes
					.getValue("sf"));
			moteConfig.setSshSFStart(attributes.getValue("sfStart"));
			moteConfig.setSshSFStop(attributes.getValue("sfStop"));
			motes.add(moteConfig);
		}
		if (localName.equals("sshConfig")) {
			sshUser = attributes.getValue("username");
			sshPassword = attributes.getValue("password");
		}
		if (localName.equals("mailConfig")) {
			emailFrom = attributes.getValue("from");
			smtpServer = attributes.getValue("smtp");
		}
		if (localName.equals("recipient"))
			recipients.add(attributes.getValue("email"));
		if (localName.equals("consumer"))
			consumers.add(attributes.getValue("class"));
	}
}
